/**
 * 回调函数的形状
 */
 interface CallbackInterface {
  (data?: any): any
}

interface UniInterface {
  navigateTo: Function
  redirectTo: Function
  reLaunch: Function
  switchTab: Function
  navigateBack: Function
  getEnv: Function
  postMessage: Function
}

declare const uni: UniInterface

interface UniJsBridgeInterFace {
  init: Function,
  send: Function,
  callHandler: Function,
  registerHandler: Function,
  uni: UniInterface
  _handleMessageFromNative: Function,
  _messageHandler?: Function
}

interface HandlersInterFace {
  [index: string]: Function
}

interface NativeToH5Message {
  callbackId?: string // Native端自己需要回调函数是ID，一般由Native端发送给H5，然后H5回传给Native
  responseId?: string // H5端回调函数的ID，一般是由H5发送给Native，然后Native回传给H5
  handlerName?: string    // messageHandlers中的key
  responseData?: string | Object  // Native传给H5的参数
}

/**
* 回调函数的形状
* data: 参数
* callback: 回调函数，执行则可以让对应的端执行回调；比如在H5端调用了Native端的功能，在H5端如果不执行callback()，那么Native
* 是不会执行回调的(Native自己定义的回调)
*/
interface ResponseCallback {
  (data?: Object | string, callback?: CallbackInterface): any
}


export {}

declare global {
  interface Window {
      UniJsBridge: UniJsBridgeInterFace
  }
}

if (window.UniJsBridge) {
  throw new Error('UniJsBridge已经存在了')
}

let uniqueId: number = 1;   // 全局唯一标识,主要用来生产callbackId
const responseCallbacks: HandlersInterFace = {};   // 给客户端发送消息后的回调函数集合
const messageHandlers: HandlersInterFace = {};     // 在H5中注册的函数（就是等待客户端调用的函数）的集合
let receiveMessageQueue: any = [];   // 消息队列，Native给H5发消息的队列，所有的消息都集中在此，依次调用

if (!uni) {
  throw new Error('uni未定义，需要先引入uni-sdk,地址为：https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js')
}

const UniJsBridge: UniJsBridgeInterFace = {
  init,
  send,
  callHandler,
  registerHandler,
  uni,
  _handleMessageFromNative: _handleMessageFromNative,
}

function init(messageHandler: Function) {
  if (UniJsBridge._messageHandler) {
      throw new Error('UniJSBridge._messageHandler 被调用了两次')
  }

  UniJsBridge._messageHandler = messageHandler

  let receiveMessage: any = receiveMessageQueue;
  receiveMessageQueue = null;

  for (let i = 0; i < receiveMessage.length; i++) {
      _dispatchMessageFromNative(receiveMessage[i])
  }
}

/**
* 发送消息给客户端
* PS:主动发送
* @param data 给客户端的数据
* @param responseCallback 回调函数
*/
function send(data: string | Object, responseCallback: ResponseCallback) {
  _doSend('send', data, responseCallback)
}

// 调用线程
function callHandler(handlerName: string, data: string | Object, responseCallback: ResponseCallback) {
  _doSend(handlerName, data, responseCallback);
}

/**
* 注册一个函数，待客户端调用
* PS:被动等待调用
* @param handlerName
* @param handler: 接收两个参数，data(客户端回复给H5的数据)，responseCallback(一个回调函数：调用该函数，客户端才可以执行回调，且可以传参数给客户端)
*/
function registerHandler(handlerName: string, handler: ResponseCallback) {
  messageHandlers[handlerName] = handler
}

/**
* 发送给客户端的核心函数，依赖uni-app SDk
* PS:主动发送
* @param handlerName
* @param message
* @param responseCallback: 可接收String和Function,如果为String的话就是给客户端的一个回调ID，这个ID其实本来就是由客户端自己传过来的
* 如果为Function的话就是H5的回调函数
* @private
*/
function _doSend(handlerName: string, message: Object | string, responseCallback: ResponseCallback | string) {
  let callbackId;

  if (typeof responseCallback === 'string') {
      callbackId = responseCallback;
  } else if (responseCallback) {
      /**
       * 把回调函数塞进集合中去，待客户端代码执行完毕就去找到对应的函数开始执行
       */
      callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
      responseCallbacks[callbackId] = responseCallback;
  } else {
      callbackId = '';
  }

  if (!uni.postMessage) {
      return console.error('此接口依赖uni.webview,具体参考：https://uniapp.dcloud.io/component/web-view')
  }

  // 给客户端发送消息
  uni.postMessage({
      data: {
          data: message,
          callbackId,
          handlerName,
      },
  });
}

function _dispatchMessageFromNative(messageJSON: NativeToH5Message) {
  if (!messageJSON) {
      return console.warn('客户端没有回复任何消息')
  }

  try {
      /**
       * responseId: 客户端回复前端的回调函数的ID
       * callbackId: 给客户端自己用的回调ID
       * handlerName: 前后端统一约定的一个名字[约定不同的名字对应不同的功能，需要后期加，这里只是封装的一个方法]
       * responseData: 客户端给前端的参数
       */
      let {callbackId, responseId, handlerName, responseData} = messageJSON
      let responseCallback

      if (responseId) {
          responseCallbacks[responseId] && responseCallbacks[responseId](responseData)
          delete responseCallbacks[responseId];
      } else {
          if (callbackId) {
              let callbackResponseId = callbackId
              responseCallback = function (responseData: Object | string) {
                  _doSend('response', responseData, callbackResponseId)
              }
          }

          let handler: any = UniJsBridge._messageHandler;
          if (handlerName) {
              handler = messageHandlers[handlerName]
          }
          handler(responseData, responseCallback)
      }
  } catch (e) {
      console.log(e)
  }
}

/**
* 提供给客户端调用的函数，
* 约定客户端只能通过此函数发消息给H5
* @param messageJSON
* @private
*/
function _handleMessageFromNative(messageJSON: NativeToH5Message) {
  if (receiveMessageQueue) {
      receiveMessageQueue.push(messageJSON)
  }
  _dispatchMessageFromNative(messageJSON)
}
// Native调用H5函数必须是要挂载到window下
window.UniJsBridge = UniJsBridge

export default  UniJsBridge;